﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace ExpressionMapper.Tests
{
    using MappingExpressionGenerator = Func<Expression, Type, Expression, Expression>;

    [TestClass]
    public class CyclicReferencesTest
    {
        public class Parent
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public List<Child> Children { get; set; }
            public Child Child { get; set; }
        }

        public class Child
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public Parent Parent { get; set; }
        }

        [TestMethod]
        public void PotentianCyclicReferenceTest()
        {
            Parent john = new Parent { Id = 1, Name = "John" };
            john.Children = new List<Child> { new Child { Id = 1, Name = "Nicole" }, new Child { Id = 2, Name = "Lisa" } };
            john.Child = new Child { Id = 1, Name = "Nicole" };

            var cloneParent = Mapper.Create<Parent, Parent>();
            Parent johnClone = cloneParent(john);
            Assert.IsNotNull(johnClone);
        }

        [TestMethod]
        public void CyclicReferenceTest()
        {
            Parent john = new Parent { Id = 1, Name = "John" };
            john.Children = new List<Child> { new Child { Id = 1, Name = "Nicole", Parent = john }, new Child { Id = 2, Name = "Lisa", Parent = john } };
            john.Child = new Child { Id = 1, Name = "Nicole", Parent = john };

            var cloneParent = Mapper.Create<Parent, Parent>();
            Debug.Flush();
            try
            {
                Parent johnClone = cloneParent(john);
                Assert.IsNotNull(johnClone);
                Assert.IsFalse(ReferenceEquals(johnClone, john));
                Assert.IsFalse(ReferenceEquals(johnClone.Child, john.Child));
                Assert.AreNotEqual(johnClone.Child.Parent, john.Child.Parent);
                Assert.AreEqual(johnClone, johnClone.Child.Parent);
            }
            catch (Exception excp)
            {
                Assert.Fail(excp.Message);
            }
        }

        [TestMethod]
        public void CyclicReferenceTest2()
        {
            try
            {
                Parent john = new Parent { Id = 1, Name = "John" };

                john.Child = new Child { Id = 1, Name = "Nicole", Parent = john };

                Parent johnClone = cloneParent(john);
                Assert.IsNotNull(johnClone);
                Assert.IsFalse(ReferenceEquals(johnClone, john));
                Assert.IsFalse(ReferenceEquals(johnClone.Child, john.Child));
                Assert.AreNotEqual(johnClone.Child.Parent, john.Child.Parent);
                Assert.AreEqual(johnClone, johnClone.Child.Parent);


                var param = Expression.Parameter(typeof(object), "obj");

                //var dictionary = Expression.Parameter(typeof(Dictionary<object, object>), "dictionary");
                //Expression.Coalesce()

                //Expression valueParam = Expression.Variable(typeof(object), "value");
                ////valueParam = Expression.Assign(valueParam, Expression.Constant(null));
                //var nothing = Expression.IsFalse(
                //                    Expression.Call(dictionary, typeof(Dictionary<object, object>).GetMethod("TryGetValue", BindingFlags.Instance | BindingFlags.Public),
                //                    param, valueParam
                //                ));

                //var dictionaryL = Expression.Lambda<Func<Dictionary<object, object>, object, bool>>(nothing, dictionary, param);

                //Dictionary<object, object> d = new Dictionary<object, object>();
                //Console.Out.WriteLine(dictionaryL.Compile()(d, new object()));

                //Expression conditionExpr = Expression.Condition(
                //    nothing,
                //           Expression.Constant("obj is new"),
                //           Expression.Constant("obj is a circular reference")
                //         );


                Expression conditionExpr2 = Expression.Condition(
                    Expression.Equal(Expression.Constant(null), param),
                           Expression.Constant("obj is null"),
                           Expression.Constant("obj is not null")
                         );

                Func<object, string> compiled = Expression.Lambda<Func<object, string>>(conditionExpr2, param).Compile();
                Console.Out.WriteLine(compiled(new object()));
                Console.Out.WriteLine(compiled(null));

            }
            catch (Exception excp)
            {
                Debug.WriteLine("{0}: {1}", excp.GetType().Name, excp.Message);
                Assert.Fail(excp.Message);
            }
        }

        private Parent cloneParent(Parent parent)
        {
            Dictionary<object, object> references = new Dictionary<object, object>();


            Func<Parent, Parent> cloneP = (p) =>
                                                    {
                                                        object copy;
                                                        if (references.TryGetValue(p, out copy))
                                                            return copy as Parent;

                                                        Parent cloned = new Parent();
                                                        references.Add(p, cloned);

                                                        cloned.Id = p.Id;
                                                        cloned.Name = p.Name;
                                                        cloned.Child = privateMapper.GetMapper<Child>()(p.Child);

                                                        return cloned;
                                                    };


            Func<Child, Child> cloneC = (c) =>
                                                    {
                                                        object copy;
                                                        if (references.TryGetValue(c, out copy))
                                                            return copy as Child;

                                                        Child cloned = new Child();
                                                        references.Add(c, cloned);
                                                        cloned.Id = c.Id;
                                                        cloned.Name = c.Name;
                                                        cloned.Parent = privateMapper.GetMapper<Parent>()(c.Parent);

                                                        return cloned;
                                                    };

            privateMapper.SetMapper(cloneP);
            privateMapper.SetMapper(cloneC);

            return privateMapper.GetMapper<Parent>()(parent);
        }

        private static class privateMapper
        {
            private static readonly Dictionary<Type, Delegate> _expressions = new Dictionary<Type, Delegate>();
            public static Func<T, T> GetMapper<T>()
            {
                Delegate expression;
                if (!_expressions.TryGetValue(typeof(T), out expression))
                    return null;
                return (Func<T, T>)expression;
            }

            public static void SetMapper<T>(Func<T, T> expression)
            {
                _expressions.Add(typeof(T), expression);
            }
        }
    }
}
